#include <bitset>
#include <string>
#include <cstring>
#include <vector>

#include <openssl/bn.h>
#include <openssl/ec.h>

#include "starkware/crypto/elliptic_curve_constants.h"

using ValueType = starkware::BigInt<4>;

int VT2BN(const ValueType& vt_x, BIGNUM* bn_x){
  std::stringstream ss;
  ss << vt_x;
  return BN_hex2bn(&bn_x, ss.str().substr(2).data());
}

ValueType BN2VT(const BIGNUM* bn_x){
  int size = BN_num_bytes(bn_x);
  unsigned char tmp[size];
  memset(tmp, 0, size);
  int num = BN_bn2bin(bn_x, tmp);

  auto vt_x = ValueType::Zero();
  auto* ptr = reinterpret_cast<unsigned char*>(&vt_x);
  for (int n = 0; n < num; n++) {
    ptr[n] = tmp[num - n - 1];
  }
  return vt_x;
}

int EcPoint2EC_POINT(
    const EC_GROUP* group,
    const starkware::EcPoint<starkware::PrimeFieldElement> sw_point,
    EC_POINT* ec_point){
  BN_CTX* bn_ctx = BN_CTX_new();
  BIGNUM* x = BN_new();
  BIGNUM* y = BN_new();

  VT2BN(sw_point.x.ToStandardForm(), x);
  VT2BN(sw_point.y.ToStandardForm(), y);
  EC_POINT_set_affine_coordinates(group, ec_point, x, y, bn_ctx);

  BN_free(x);
  BN_free(y);
  BN_CTX_free(bn_ctx);
  return 1;
}

int GetPointFromX(
    const EC_GROUP* group,
    const BIGNUM* x, 
    const BIGNUM* alpha, 
    const BIGNUM* beta, 
    const BIGNUM* mod,
    EC_POINT* public_key){
  BIGNUM* y = BN_new();
  BIGNUM* ax = BN_new();
  BN_CTX* bn_ctx = BN_CTX_new();
  BN_mod_mul(ax, alpha, x, mod, bn_ctx);

  BN_mod_sqr(y, x, mod, bn_ctx);
  BN_mod_mul(y, y, x, mod, bn_ctx);
  BN_mod_add_quick(y, y, ax, mod);
  BN_mod_add_quick(y, y, beta, mod);
  BN_mod_sqrt(y, y, mod, bn_ctx);

  EC_POINT_set_affine_coordinates(group, public_key, x, y, bn_ctx);

  BN_free(y);
  BN_free(ax);
  BN_CTX_free(bn_ctx);
  return 1;
}

int EcConstants2EC_POINT(
    const EC_GROUP* group, 
    EC_POINT* shift_point,
    std::vector<EC_POINT*> points){
  const auto& consts = starkware::GetEcConstants();
  EcPoint2EC_POINT(group, consts.k_points[0], shift_point); // P0
  for (size_t j = 0; j < points.size(); j++) {
    // pre-calculated values of P1, P2, P3, P4 (consts.k_points[1] is generator point)
    EcPoint2EC_POINT(group, consts.k_points[j + 2], points[j]);
  }
  return 1;
}
